/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering, 
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 *GENERATED USING SOFTWARE.
 */
package cgl.narada.samples.rtp;

import java.io.IOException;

import javax.media.Controller;
import javax.media.ControllerClosedEvent;
import javax.media.ControllerEvent;
import javax.media.ControllerListener;
import javax.media.Format;
import javax.media.MediaLocator;
import javax.media.NoProcessorException;
import javax.media.Processor;
import javax.media.control.TrackControl;
import javax.media.format.VideoFormat;
import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.DataSource;

/**
 * This MediaCapturer class is used to capture media from a device
 * The given MediaLocator shows the device.
 * Indeed that Medialocator does not have to be a device, but any MediaLocator
 *
 * @author Ahmet Uyar
 * $Date$
 */

public class MediaCapturer {

   private MediaLocator locator;
   private Processor processor;

   private Integer stateLock = new Integer(0);
   private boolean failed = false;

   public MediaCapturer(MediaLocator locator, ContentDescriptor cd, Format outputFormat) {
      this.locator = locator;

      // Create a processor for the specified media locator
      String result = createProcessor(cd, outputFormat);

      if (result != null) {
         System.out.println("Can not initialize processor: " + result);
      }
   }

   public DataSource getDataSource() {
      if (processor == null) {
         return null;
      }

      // first start the processor
      processor.start();
      return processor.getDataOutput();
   }

   /**
    * Stops reading if already started
    */
   public void stop() {
      synchronized (this) {
         if (processor != null) {
            processor.stop();
            processor.close();
            processor = null;
         }
      }
   }

   /**
    * creates processor with the given ContentDescriptor and Format
    *
    * returns null if successful
    */
   private String createProcessor(ContentDescriptor cd, Format outputFormat) {
      if (locator == null) {
         return "Locator is null";
      }

      DataSource ds;

      try {
         ds = javax.media.Manager.createDataSource(locator);
      } catch (Exception e) {
         e.printStackTrace();
         return "Couldn't create DataSource";
      }

      // Try to create a processor to handle the input media locator
      try {
         processor = javax.media.Manager.createProcessor(ds);
      } catch (NoProcessorException npe) {
         return "Couldn't create processor";
      } catch (IOException ioe) {
         return "IOException creating processor";
      }

      // Wait for it to configure
      boolean result = waitForState(processor, Processor.Configured);

      if (result == false) {
         return "Couldn't configure processor";
      }

      // Get the tracks from the processor
      TrackControl[] tracks = processor.getTrackControls();

      // Do we have atleast one track?
      if ((tracks == null) || (tracks.length < 1)) {
         return "Couldn't find tracks in processor";
      }

      if(tracks.length > 1)
         return "Can not handle more than one track. There are "+tracks.length+" tracks.";

      // Set the output content descriptor to cd
      // This will limit the supported formats reported from Track.getSupportedFormats
      processor.setContentDescriptor(cd);
      Format[] supportedFormats = tracks[0].getSupportedFormats();;

      // Program the tracks.
      boolean formatSet = false;
      for (int i = 0; i < supportedFormats.length; i++) {
         if(supportedFormats[i].matches(outputFormat)){
            tracks[0].setFormat(outputFormat);
            formatSet = true;
            i = supportedFormats.length;
         }
      }

      if (!formatSet) {
         return "The given format is not supported";
      }


      // Realize the processor. This will internally create a flow
      // graph and attempt to create an output datasource for JPEG/RTP
      // audio frames.
      result = waitForState(processor, Controller.Realized);

      if (result == false) {
         return "Couldn't realize processor";
      }

      return null;
   }

   Integer getStateLock() {
      return stateLock;
   }

   void setFailed() {
      failed = true;
   }

   private synchronized boolean waitForState(Processor p, int state) {
      p.addControllerListener(new StateListener());
      failed = false;

      // Call the required method on the processor
      if (state == Processor.Configured) {
         p.configure();
      } else if (state == Processor.Realized) {
         p.realize();
      }

      // Wait until we get an event that confirms the
      // success of the method, or a failure event.
      // See StateListener inner class
      while ((p.getState() < state) && !failed) {
         synchronized (getStateLock()) {
            try {
               getStateLock().wait();
            } catch (InterruptedException ie) {
               return false;
            }
         }
      }

      if (failed) {
         return false;
      } else {
         return true;
      }
   }

   /****************************************************************
    * Inner Classes
    ****************************************************************/
   class StateListener implements ControllerListener {
      public void controllerUpdate(ControllerEvent ce) {
         // If there was an error during configure or
         // realize, the processor will be closed
         if (ce instanceof ControllerClosedEvent) {
            setFailed();
         }

         // All controller events, send a notification
         // to the waiting thread in waitForState method.
         if (ce instanceof ControllerEvent) {
            synchronized (getStateLock()) {
               getStateLock().notifyAll();
            }
         }
      }
   }

   /****************************************************************
    * Sample Usage for MediaCapturer class
    ****************************************************************/
   public static void main(String[] args) {

      String captureLocator = "vfw://0";

      ContentDescriptor cd = new ContentDescriptor(ContentDescriptor.RAW);
      VideoFormat vf = new VideoFormat(VideoFormat.RGB);
      MediaLocator ml = new MediaLocator(captureLocator);
      MediaCapturer mfr = new MediaCapturer(ml, cd, vf);
      DataSource dataSource = mfr.getDataSource();

      new PlayerWindow(dataSource);
   }

}
